using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

using LuaInterface;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.Windows;

using miciv.Rendering;
using miciv.Misc;

namespace miciv
{
    public class UIManager : IUIManager
    {
        protected string m_strName;
        protected CoreContext m_coreContext;
        protected Hashtable m_htblScenes;
        protected Vector2 m_v2ScreenSize;
        protected Stack<Scene> m_stackScenes;
        protected Scene m_activeScene;
        protected Frame m_selectedFrame;
        protected LuaManager m_luaManager;
        protected Hashtable m_htblTemplates;
        protected Hashtable m_htblDataSources;
        protected CoreManager m_coreManager;
        protected IRendererContext m_defaultRendererContext;
        protected List<IRendererContext> m_lstRendererContextes;
        protected List<Text> m_lstDebugTexts;
        protected DebugDataSource m_debugDataSource;

        public UIManager()
        {
            this.m_strName = "UIManager";
            this.m_htblScenes = new Hashtable();
            this.m_v2ScreenSize = new Vector2(0.0f, 0.0f);
            this.m_stackScenes = new Stack<Scene>();
            this.m_activeScene = null;
            this.m_selectedFrame = null;
            this.m_luaManager = null;
            this.m_htblTemplates = new Hashtable();
            this.m_htblDataSources = new Hashtable();
            this.m_coreManager = null;
            this.m_lstRendererContextes = new List<IRendererContext>();
            this.m_lstDebugTexts = new List<Text>();
            this.m_debugDataSource = new DebugDataSource();
        }

        protected void ScreenSizeChanged(int _sWidth, int _sHeight)
        {
            this.m_defaultRendererContext.Camera.SetOrtho(_sWidth, _sHeight, 0.1f, 1000.0f);
            this.Width = _sWidth;
            this.Height = _sHeight;
        }

        #region IUIManager Membres

        public string Name
        {
            get
            {
                return this.m_strName;
            }
        }

        public float Width
        {
            get
            {
                return this.m_v2ScreenSize.X;
            }
            set
            {
                this.m_v2ScreenSize.X = value;
            }
        }

        public float Height
        {
            get
            {
                return this.m_v2ScreenSize.Y;
            }
            set
            {
                this.m_v2ScreenSize.Y = value;
            }
        }

        public bool Initialize(CoreContext _coreContext, float _fWidth, float _fHeight)
        {
            this.m_coreContext = _coreContext;
            this.m_coreManager = this.m_coreContext.CoreManager;
            this.Width = _fWidth;
            this.Height = _fHeight;
            this.m_luaManager = this.m_coreContext.GetData<LuaManager>("LuaManager");

            // setup defaut renderer context for all things ui
            this.m_defaultRendererContext = new RendererContext(this.m_coreContext);
            this.m_defaultRendererContext.ID = "uidefault";
            this.m_defaultRendererContext.Camera = new CameraBase(this.m_coreContext);
            this.m_defaultRendererContext.Camera.SetOrtho(this.Width, this.Height, 1.0f, 1000.0f);
            this.m_defaultRendererContext.Camera.Position = new Vector3(0.0f, 0.0f, -10.0f);
            this.m_defaultRendererContext.Camera.Rotation = new Vector3(0.0f, 0.0f, 0.0f);
            this.m_defaultRendererContext.ScreenSizeChanged = this.ScreenSizeChanged;
            this.m_defaultRendererContext.ClearOptions.Enabled = true;
            this.m_defaultRendererContext.ClearOptions.Depth = 1.0f;
            this.m_defaultRendererContext.ClearOptions.Flags = ClearFlags.ZBuffer;
            this.m_defaultRendererContext.BeginScene += this.BeginScene;
            this.m_defaultRendererContext.EndScene += this.EndScene;

            // declare created renderer context to renderer manager for automatic management
            this.m_coreContext.GetData<RendererManager>("RendererManager").AddRendererContext(this.m_defaultRendererContext);

            // create restricted renderer contextes list for all level elements
            this.m_lstRendererContextes.Add(this.m_defaultRendererContext);

            return true;
        }

        public bool LoadScene(string _strLocation)
        {
            _strLocation = this.m_coreContext.CoreManager.GetExistingFile(_strLocation);
            XmlDocument xmlDoc = this.m_coreContext.CoreManager.LoadXml(_strLocation, true);
            XmlTraverser xmlTraverser = new XmlTraverser(xmlDoc.DocumentElement);
            XmlListenerUI uiCreator = new XmlListenerUI();

            // load script file because some of it will be linked to created frames
            _strLocation = Path.ChangeExtension(_strLocation, "lua");
            if (false != File.Exists(_strLocation))
            {
                this.m_coreContext.GetData<LuaManager>("LuaManager").DoFile(_strLocation);
            }

            uiCreator.Initialize(this, this.m_coreContext, xmlTraverser);
            uiCreator.Attach();
            xmlTraverser.Traverse();
            uiCreator.Detach();

            return true;
        }

        public Scene GetScene(string _strScene)
        {
            if (false != this.m_htblScenes.ContainsKey(_strScene))
            {
                return this.m_htblScenes[_strScene] as Scene;
            }
            return null;
        }

        public bool AddScene(Scene _scene)
        {
            if (false == this.m_htblScenes.ContainsKey(_scene.ID))
            {
                if (null == _scene.RestrictedRendererContexts)
                {
                    _scene.RestrictedRendererContexts = this.m_lstRendererContextes;
                }
                this.m_htblScenes.Add(_scene.ID, _scene);
                return true;
            }
            return false;
        }

        public bool ActivateScene(string _strScene, bool _bPushCurrentActiveScene, bool _bShowPushedScene)
        {
            Scene scene = GetScene(_strScene);
            if (null != scene)
            {
                if (false != _bPushCurrentActiveScene)
                {
                    this.m_stackScenes.Push(this.m_activeScene);
                    if (null != this.m_activeScene)
                    {
                        this.m_activeScene.Visible = _bShowPushedScene;
                    }
                }
                else if (null != this.m_activeScene)
                {
                    this.m_activeScene.Visible = false;
                }
                if (null != this.m_activeScene)
                {
                    RunFrameEvent(this.m_activeScene, "deactivate");
                }
                this.m_activeScene = scene;
                this.m_activeScene.Visible = true;
                RunFrameEvent(this.m_activeScene, "activate");
                return true;
            }
            return false;
        }

        public void DeactivateScene(bool _bPushCurrentActiveScene, bool _bShowPushedScene)
        {
            if (null != this.m_selectedFrame)
            {
                RunFrameEvent(this.m_selectedFrame, "unselected");
            }
            if (null != this.m_activeScene)
            {
                if (false != _bPushCurrentActiveScene)
                {
                    this.m_stackScenes.Push(this.m_activeScene);
                    this.m_activeScene.Visible = _bShowPushedScene;
                }
                else
                {
                    this.m_activeScene.Visible = false;
                }
                RunFrameEvent(this.m_activeScene, "deactivate");
            }
            this.m_activeScene = null;
            this.m_selectedFrame = null;
        }

        public void PopPreviousScene()
        {
            if (null != this.m_selectedFrame)
            {
                RunFrameEvent(this.m_selectedFrame, "unselected");
            }
            if (null != this.m_activeScene)
            {
                RunFrameEvent(this.m_activeScene, "deactivate");
                this.m_activeScene.Visible = false;
            }
            this.m_activeScene = this.m_stackScenes.Pop();
            if (null != this.m_activeScene)
            {
                this.m_activeScene.Visible = true;
                RunFrameEvent(this.m_activeScene, "activate");
            }
            this.m_selectedFrame = null;
        }

        public void ClearScenesStack()
        {
            this.m_stackScenes.Clear();
        }

        public bool AddTemplate(Template _template)
        {
            if (false == this.m_htblTemplates.ContainsKey(_template.ID))
            {
                this.m_htblTemplates.Add(_template.ID, _template);
                return true;
            }
            return false;
        }

        public Template GetTemplate(string _strTemplate)
        {
            if (false != this.m_htblTemplates.ContainsKey(_strTemplate))
            {
                return this.m_htblTemplates[_strTemplate] as Template;
            }
            return null;
        }

        public void Update(float _fTime)
        {
            if (null != this.m_activeScene)
            {
                Frame selectedFrame = this.m_selectedFrame;
                MouseInput mi = this.m_coreContext.GetData<MouseInput>("MouseInput");
                if ((0 != mi.DeltaX) || (0 != mi.DeltaY))
                {
                    Stack<Frame> pickedFrames = new Stack<Frame>();
                    Vector3 f3Camera = this.m_defaultRendererContext.Camera.Unproject(mi.X, mi.Y);
                    float fCX = f3Camera.X;
                    float fCY = f3Camera.Y;
                    this.m_activeScene.PickChildren(pickedFrames, fCX, fCY);
                    // get the last pushed descending child since it's the more accurate/precise picked one
                    if (0 < pickedFrames.Count)
                    {
                        selectedFrame = pickedFrames.Pop();
                    }
                    else
                    {
                        selectedFrame = null;
                    }
                }
                if (this.m_selectedFrame != selectedFrame)
                {
                    if (null != this.m_selectedFrame)
                    {
                        RunFrameEvent(this.m_selectedFrame, "unselected");
                    }
                    this.m_selectedFrame = selectedFrame;
                    if (null != this.m_selectedFrame)
                    {
                        RunFrameEvent(this.m_selectedFrame, "selected");
                    }
                }
                if (null != this.m_selectedFrame)
                {
                    if (false != mi.JustReleasedButtons[0])
                    {
                        RunFrameEvent(this.m_selectedFrame, "action");
                    }
                    else if (false != mi.JustReleasedButtons[1])
                    {
                        RunFrameEvent(this.m_selectedFrame, "cancel");
                    }
                }
            }
            foreach (DictionaryEntry de in this.m_htblScenes)
            {
                Scene scene = de.Value as Scene;
                if (false != scene.Visible)
                {
                    scene.UpdateSize();
                }
            }
        }

        public void StopApp()
        {
            
        }

        protected void RunFrameEvent(Frame _frame, string _strEventName)
        {
            try
            {
                if (null != _frame.ScriptControl)
                {
                    string strFuncPath = string.Format("{0}.events.{1}", _frame.ScriptControlPath, _strEventName);
                    LuaFunction lfuncEvent = this.m_luaManager.GetFunction(strFuncPath);
                    if (null != lfuncEvent)
                    {
                        LuaTable ltblControl = _frame.ScriptControl as LuaTable;
                        this.m_luaManager.State["SelectedControl"] = _frame;
                        lfuncEvent.Call();
                        this.m_luaManager.State["SelectedControl"] = null;
                    }
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
            }
        }

        public bool UnloadScene(string _strScene)
        {
            Scene scene = this.GetScene(_strScene);
            if (false != this.m_stackScenes.Contains(scene))
            {
                if (scene == this.m_activeScene)
                {
                    this.m_activeScene = null;
                    this.m_selectedFrame = null;
                }
                if (null != scene)
                {
                    scene.Release();
                    this.m_htblScenes.Remove(_strScene);
                    return true;
                }
            }
            return false;
        }

        public void UnloadScenes()
        {
            foreach (DictionaryEntry de in this.m_htblScenes)
            {
                (de.Value as Scene).Release();
            }
            this.m_htblTemplates.Clear();
            this.m_htblScenes.Clear();
            this.m_stackScenes.Clear();
            this.m_activeScene = null;
            this.m_selectedFrame = null;
        }

        public void DebugText(Vector2 _f2Position, Color4 _c4Diffuse, string _strText)
        {
            bool bDone = false;
            foreach (Text text in this.m_lstDebugTexts)
            {
                if (false == text.Visible)
                {
                    text.Visible = true;
                    text.Diffuse = _c4Diffuse;
                    text.Position = _f2Position;
                    text.Position += new Vector2(-this.Width / 2.0f, this.Height / 2.0f);
                    text.DataSourceIndex = this.m_debugDataSource.Count;
                    this.m_debugDataSource.Add(_strText);
                    bDone = true;
                    break;
                }
            }
            if (false == bDone)
            {
                Text text = new Text(this.m_coreContext, string.Format("debugtext_{0}", this.m_lstDebugTexts.Count));
                text.Visible = true;
                text.Diffuse = _c4Diffuse;
                text.Position = _f2Position;
                text.Position += new Vector2(-this.Width / 2.0f, this.Height / 2.0f);
                text.DataSource = this.m_debugDataSource;
                text.DataSourceIndex = this.m_debugDataSource.Count;
                text.StringID = "oneparamtext";
                text.AddParameter("name", "{0}");
                text.RestrictedRendererContexts = this.m_lstRendererContextes;
                this.m_debugDataSource.Add(_strText);
                this.m_lstDebugTexts.Add(text);
            }
        }

        protected void BeginScene(IRendererContext _rendererContext)
        {
            foreach (DictionaryEntry de in this.m_htblScenes)
            {
                Scene scene = de.Value as Scene;
                if (false != scene.Visible)
                {
                    scene.AddToRenderer();
                }
            }
            foreach (Text text in this.m_lstDebugTexts)
            {
                if (false != text.Visible)
                {
                    text.Renderer.Add(text);
                }
            }
        }

        protected void EndScene(IRendererContext _rendererContext)
        {
            foreach (Text text in this.m_lstDebugTexts)
            {
                text.Visible = false;
            }
            this.m_debugDataSource.Clear();
        }

        #endregion
    }
}
